#include <AnyToAny.h>
#include "configure.h"
#include "Curve.h"

class CElement
{
public:
	double original_x,original_y;//original
	double x;
	double y;
	double dx;
	double dy;

	double red;
	double green;
	double blue;
};
CElement g_element[m_element_count];
static const double m_radius=4;
static const double m_acceleration_per_force=0.01;
static const double m_friction=0.97;

static const double g_big_size_times=2;
class CBigBall
{
public:
	double x;
	double y;
	double dx;
	double dy;
};
CBigBall g_big_ball;
static const double g_big_ball_speed=5;

CCurve g_curve_x,g_curve_y,g_curve_red,g_curve_green,g_curve_blue;

CImage g_background;
CImage g_buffer_image;
CImage g_finite_element_foreground_copy;
CImage g_finite_element_foreground;
CImage g_finite_element_image;
CImage g_direction_image;
CImage g_ball_image;
CSound g_turn,g_boom;
static const int g_shot_o_x=0;
static const int g_shot_o_y=63;
static const int g_target_left=321; 
int g_target_width;
static const int g_target_top=18;
int g_target_height;
int g_mouse_x=100;
int g_mouse_y=100;
double g_shot_conner=PI/4;
void GetForce(double &acceleration_x,double &acceleration_y,CElement &dest,CElement &source)
{
	double x_from_original,y_from_original;
	x_from_original=source.x-source.original_x+dest.original_x;
	y_from_original=source.y-source.original_y+dest.original_y;
	double delta_x,delta_y;
	delta_x=-x_from_original+dest.x;
	delta_y=-y_from_original+dest.y;
	acceleration_x=-delta_x*m_acceleration_per_force;
	acceleration_y=-delta_y*m_acceleration_per_force;	
}
void Initialize()
{
	g_curve_x.Create(m_element_count);
	g_curve_y.Create(m_element_count);
	g_curve_red.Create(m_element_count);
	g_curve_green.Create(m_element_count);
	g_curve_blue.Create(m_element_count);

	int index;
	for(index=0;index<m_element_count;index++)
	{
		CElement &element=g_element[index];
		element.original_x=element.x=(m_width-1)*index/(m_element_count-1);
		int random;
		random=rand()%100;
		element.red=  (151+(-151+ 47)*random/(100-1));
		element.green=(184+(-184+153)*random/(100-1));
		element.blue= (229+(-229+216)*random/(100-1));
		element.original_y=element.y=m_height-78+0.13*(random-100/2);
		element.dx=0;
		element.dy=-0.03*(random-100/2);
	}


	g_big_ball.x=m_width+100;
	g_big_ball.y=0;
	g_big_ball.dx=0;
	g_big_ball.dy=0;

	{
		g_finite_element_foreground.Copy(g_finite_element_foreground_copy);
		for(index=0;index<m_element_count;index++)
		{
			g_curve_red.Set(index,g_element[index].red);
			g_curve_green.Set(index,g_element[index].green);
			g_curve_blue.Set(index,g_element[index].blue);
		}
		g_curve_red.Go();
		g_curve_green.Go();
		g_curve_blue.Go();
		CImage line;
		int height;
		height=g_finite_element_foreground.Height();
		line.SetSize(1,height);
		for(index=0;index<m_width;index++)
		{
			double t;
			t=(m_element_count-1)*index/((double)(m_width-1));
			unsigned int red,green,blue;
			red=(unsigned int)g_curve_red.Get(t);
			green=(unsigned int)g_curve_green.Get(t);
			blue=(unsigned int)g_curve_blue.Get(t);
			line.SetColor(CImage::Color(red,green,blue,40*255/100));
			line.DrawRectangle(0,0,0,height-1);
			g_finite_element_foreground.Merge(index,0,line,0,0,1,height,MERGE_IMAGE_COVER);
		}	
	}
}
void Step()
{
	{
		int index;
		for(index=1;index<m_element_count-1;index++)
		{
			CElement &element=g_element[index];
			element.x+=element.dx;
			element.y+=element.dy;
		}
		g_big_ball.x+=g_big_ball.dx;
		g_big_ball.y+=g_big_ball.dy;

		if(g_target_left<g_big_ball.x&&g_big_ball.x<g_target_left+g_target_width)
			if(g_target_top<g_big_ball.y&&g_big_ball.y<g_target_top+g_target_height)
			{
				g_big_ball.x=m_width+100;
				g_big_ball.dx=0;
				g_big_ball.dy=0;
				g_boom.Play();
			}

	}
	{
		double min_distance;
		min_distance=1.7E308;//MAX_DOUBLE;
		CElement *min_distance_left,*min_distance_right;
		int index;
		for(index=0;index<m_element_count-1;index++)
		{
			CElement &left=g_element[index];
			CElement &right=g_element[index+1];
			double x,y;
			x=(left.x+right.x)/2.0;
			y=(left.y+right.y)/2.0;
			double delta_x,delta_y;
			delta_x=-g_big_ball.x+x;
			delta_y=-g_big_ball.y+y;
			double distance;
			distance=pow(delta_x,2.0)+pow(delta_y,2.0);
			if(distance<min_distance)
			{
				min_distance=distance;
				min_distance_left=&left;
				min_distance_right=&right;
			}
		}
		min_distance=sqrt(min_distance);
		double limit_distance;
		limit_distance=m_radius*(1+g_big_size_times)+g_big_ball_speed/2;
		if(min_distance<limit_distance)
		{
			double delta_x,delta_y;
			delta_x=(-min_distance_left->x+min_distance_right->x);
			delta_y=(-min_distance_left->y+min_distance_right->y);
			double left_right_distance;
			left_right_distance=sqrt(pow(delta_x,2.0)+pow(delta_y,2.0));
			if(left_right_distance!=0)
			{
				{
					double swap;
					swap=delta_x;
					delta_x=delta_y;
					delta_y=swap;
				}
				delta_x/=left_right_distance;
				delta_y/=left_right_distance;
				delta_y=-delta_y;

				double normal;
				normal=acos(delta_x);
				if(delta_y<0)
					normal=PI*2-normal;

				double direction;
				direction=acos(g_big_ball.dx/g_big_ball_speed);
				if(g_big_ball.dy<0)
					direction=PI*2-direction;

				direction=normal+(normal-direction);

				double force_x;
				double force_y;
				force_x=g_big_ball_speed*cos(direction);
				force_y=g_big_ball_speed*sin(direction);

				double left_weigth;
				left_weigth=sqrt(pow(min_distance_right->x-g_big_ball.x,2)+pow(min_distance_right->y-g_big_ball.y,2));
				double right_weigth;
				right_weigth=sqrt(pow(min_distance_left->x-g_big_ball.x,2)+pow(min_distance_left->y-g_big_ball.y,2));
				double sum_weigth;
				sum_weigth=left_weigth+right_weigth;

				min_distance_left->dx +=0.5*force_x*left_weigth/sum_weigth;
				min_distance_left->dy +=2*force_y*left_weigth/sum_weigth;
				min_distance_right->dx+=0.5*force_x*right_weigth/sum_weigth;
				min_distance_right->dy+=2*force_y*right_weigth/sum_weigth;

				g_big_ball.dx=-force_x;
				g_big_ball.dy=-force_y;

				g_big_ball.y-=limit_distance;
				g_turn.Play();
			}
		}
	}
	{
		int index;
		for(index=1;index<m_element_count-1;index++)
		{
			CElement &element=g_element[index];
			double acceleration_x_sum,acceleration_y_sum;
			acceleration_x_sum=acceleration_y_sum=0;
			int loop;
			for(loop=-1;loop<=1;loop+=2)
			{				
				double acceleration_x,acceleration_y,acceleration_rotate;
				GetForce(acceleration_x,acceleration_y,element,g_element[index+loop]);
				acceleration_x_sum+=acceleration_x;
				acceleration_y_sum+=acceleration_y;
			}//end of loop
			element.dx*=m_friction;
			element.dy*=m_friction;
			element.dx+=acceleration_x_sum;
			element.dy+=acceleration_y_sum;
		}
	}
}
void PrintSplineStep(int x1,int x2,double t)
{
	int current_y;
	current_y=g_curve_y.Get(t)+0.5;
	if(current_y<m_height)
	{
		CImage line;
		int line_width;
		line_width=-x1+x2+1;
		static const int line_height=78;
		line.SetSize(line_width,line_height);
		line.Merge(0,0,g_finite_element_foreground,x1,0,line_width,line_height,MERGE_IMAGE_COPY);
		int scaled_height;
		scaled_height=m_height-current_y;
		line.ScaleY(scaled_height);
		g_finite_element_image.Merge(x1,current_y,line,0,0,line_width,scaled_height,MERGE_IMAGE_COVER);
	}
}
void PrintSpline()
{
	g_finite_element_image.SetColor(0);
	g_finite_element_image.DrawRectangle(0,0,m_width,m_height);

	{
		int index;
		for(index=0;index<m_element_count;index++)
		{
			g_curve_x.Set(index,g_element[index].x);
			g_curve_y.Set(index,g_element[index].y);
		}
		g_curve_x.Go();
		g_curve_y.Go();
	}

	int next_start_x;
	next_start_x=0;
	int x;
	x=0;
	static const int step=3;
	int loop;
	for(loop=step/2;loop<m_width;loop+=step)
	{
		double t;
		t=(m_element_count-1)*loop/((double)(m_width-1));
		int current_x;
		current_x=(g_curve_x.Get(t)+0.5);
		int start_x;
		start_x=next_start_x;
		int end_x;
		end_x=current_x+step/2;
		if(end_x>=start_x)
		{
			PrintSplineStep(start_x,end_x,t);
			next_start_x=end_x+1;
		}
	}
	g_buffer_image.Merge(0,0,g_finite_element_image,0,0,m_width,m_height,MERGE_IMAGE_COVER);
}
static const int m_big_ball_diameter=m_radius*g_big_size_times*2;
void Print()
{
	g_buffer_image.Merge(0,0,g_background,0,0,m_width,m_height,MERGE_IMAGE_COPY);
	PrintSpline();

	{
		int dx,dy;
		dx=-g_shot_o_x+g_mouse_x;
		dy=-g_shot_o_y+g_mouse_y;
		double radius;
		radius=sqrt((double)(dx*dx+dy*dy));
		if(radius!=0)
		{
			g_shot_conner=dx/radius;
			g_shot_conner=acos(g_shot_conner);
			if(dy<0)
			{
				g_shot_conner=PI*2-g_shot_conner;
			}
		}
		else
		{
			g_shot_conner=0;
		}

		if(g_shot_conner<PI/20)
		{
			g_shot_conner=PI/20;
		}
		if(g_shot_conner>PI/2-PI/20)
		{
			g_shot_conner=PI/2-PI/20;
		}

		CImage image;
		int size;
		size=g_direction_image.Width()*2;
		image.SetSize(size,size);
		image.SetColor(0);
		image.SetBackgroundColor(0);
		image.DrawRectangle(0,0,size,size);
		image.Merge(g_direction_image.Width(),(size-g_direction_image.Height())/2,g_direction_image,0,0,g_direction_image.Width(),g_direction_image.Height(),MERGE_IMAGE_COVER);
		image.Rotate(g_shot_conner);
		g_buffer_image.Merge(8,74,image,size/2,size/2,size/2,size/2,MERGE_IMAGE_COVER);
	}

	//debug [
	/*
	{
		g_buffer_image.SetColor(CImage::Color(155,123,200));
		int index;
		for(index=0;index<m_element_count;index++)
		{
			CElement &element=g_element[index];
			g_buffer_image.Ellipse(element.x-3,element.y-3,element.x+3,element.y+3);
		}
	}
	*/
	//debug ]

	{
		int width,height;
		width=g_ball_image.Width();
		height=g_ball_image.Height();
		int left,top;
		left=g_big_ball.x-width/2;
		top=g_big_ball.y-height/2;
		g_buffer_image.Merge(left,top,g_ball_image,0,0,width,height,MERGE_IMAGE_COVER);
	}
	g_desktop_window.Merge(0,0,g_buffer_image,0,0,m_width,m_height,MERGE_IMAGE_COPY);
}
void __stdcall MouseMoveHandler(int x,int y,int flag)
{
	if(x<g_shot_o_x)
		x=g_shot_o_x;
	if(y<g_shot_o_y)
		y=g_shot_o_y;
	g_mouse_x=x;
	g_mouse_y=y;
}
void __stdcall MouseLeftButtonUpHandler(int x,int y,int flag)
{
	g_big_ball.x=g_shot_o_x+100*cos(g_shot_conner);
	g_big_ball.y=g_shot_o_y+100*sin(g_shot_conner);
	g_big_ball.dx=g_big_ball_speed*cos(g_shot_conner);
	g_big_ball.dy=g_big_ball_speed*sin(g_shot_conner);
}
void Start()
{
	{
		CRemoteFile remote_file;
		remote_file.Load(SERVER_PATH _T("background.jpg"),true);
		g_background.Decode(remote_file);
		g_buffer_image.SetSize(m_width,m_height);
		g_finite_element_image.SetSize(m_width,m_height);

		remote_file.Load(SERVER_PATH _T("ElementForeground.png"),true);
		g_finite_element_foreground_copy.Decode(remote_file);

		remote_file.Load(SERVER_PATH _T("shot.png"),true);
		CImage image;
		image.Decode(remote_file);
		g_background.Merge(g_shot_o_x,g_shot_o_y,image,0,0,image.Width(),image.Height(),MERGE_IMAGE_COVER);

		remote_file.Load(SERVER_PATH _T("direction.png"),true);
		g_direction_image.Decode(remote_file);

		remote_file.Load(SERVER_PATH _T("ball.png"),true);
		g_ball_image.Decode(remote_file);

		remote_file.Load(SERVER_PATH _T("target.png"),true);
		image.Decode(remote_file);
		g_target_width=image.Width();
		g_target_height=image.Height();
		g_background.Merge(g_target_left,g_target_top,image,0,0,g_target_width,g_target_height,MERGE_IMAGE_COVER);

		remote_file.Load(SERVER_PATH _T("turn.mp3"),true);
		g_turn.Decode(remote_file);
		remote_file.Load(SERVER_PATH _T("boom.mp3"),true);
		g_boom.Decode(remote_file);
	}
	Initialize();
	{
		union
		{
			void * in;
			FMouseMoveHandler out;
		}translate;
		translate.in=MouseMoveHandler;
		g_desktop_window.RegisterMouseMoveEvent(NULL,translate.out);
	}
	{
		union
		{
			void * in;
			FMouseLeftButtonUpHandler out;
		}translate;
		translate.in=MouseLeftButtonUpHandler;
		g_desktop_window.RegisterMouseLeftButtonUpEvent(NULL,translate.out);
	}
	while(1)
	{
		Step();
		Print();
		Sleep(1);
	}
}
